#!/usr/bin/env bash
#A script written by Benexl in September 2024 under MIT LICENSE

CLI_HEADER='
██╗░░░██╗████████╗░░░░░░██╗░░██╗
╚██╗░██╔╝╚══██╔══╝░░░░░░╚██╗██╔╝
░╚████╔╝░░░░██║░░░█████╗░╚███╔╝░
░░╚██╔╝░░░░░██║░░░╚════╝░██╔██╗░
░░░██║░░░░░░██║░░░░░░░░░██╔╝╚██╗
░░░╚═╝░░░░░░╚═╝░░░░░░░░░╚═╝░░╚═╝
'

# CLI_NAME=${0##*/}
CLI_NAME="${YT_X_APP_NAME:-yt-x}"

CLI_VERSION="0.4.5"
CLI_AUTHOR="Benexl"
CLI_DIR="$(dirname "$(realpath "$0")")"
CLI_CONFIG_DIR="${XDG_CONFIG_HOME:-"$HOME"/.config}/$CLI_NAME"
CLI_EXTENSION_DIR="$CLI_CONFIG_DIR/extensions"
CLI_CACHE_DIR="${XDG_CACHE_HOME:-"$HOME"/.cache}/$CLI_NAME"
CLI_PREVIEW_IMAGES_CACHE_DIR="$CLI_CACHE_DIR/preview_images"
CLI_YT_DLP_ARCHIVE="$CLI_CACHE_DIR/yt-dlp-archive"
CLI_AUTO_GEN_PLAYLISTS="$CLI_CACHE_DIR/playlists"
CLI_PREVIEW_SCRIPTS_DIR="$CLI_CACHE_DIR/preview_text"
[ -d "$CLI_CONFIG_DIR" ] || mkdir -p "$CLI_CONFIG_DIR"
[ -d "$CLI_EXTENSION_DIR" ] || mkdir -p "$CLI_EXTENSION_DIR"
[ -d "$CLI_PREVIEW_IMAGES_CACHE_DIR" ] || mkdir -p "$CLI_PREVIEW_IMAGES_CACHE_DIR"
[ -d "$CLI_PREVIEW_SCRIPTS_DIR" ] || mkdir -p "$CLI_PREVIEW_SCRIPTS_DIR"
[ -d "$CLI_YT_DLP_ARCHIVE" ] || mkdir -p "$CLI_YT_DLP_ARCHIVE"
[ -d "$CLI_AUTO_GEN_PLAYLISTS" ] || mkdir -p "$CLI_AUTO_GEN_PLAYLISTS"
CLI_SUPPORT_PROJECT_URL="https://buymeacoffee.com/benexl"

case "$(uname -a)" in
*ndroid) PLATFORM="android" ;;
*Darwin*) PLATFORM="mac" ;;
*MINGW* | *WSL2*) PLATFORM="windows" ;;
*) PLATFORM="linux" ;;
esac

print_config() {
  echo "\
#
#    ██╗░░░██╗████████╗░░░░░░██╗░░██╗  ░█████╗░░█████╗░███╗░░██╗███████╗██╗░██████╗░
#    ╚██╗░██╔╝╚══██╔══╝░░░░░░╚██╗██╔╝  ██╔══██╗██╔══██╗████╗░██║██╔════╝██║██╔════╝░
#    ░╚████╔╝░░░░██║░░░█████╗░╚███╔╝░  ██║░░╚═╝██║░░██║██╔██╗██║█████╗░░██║██║░░██╗░
#    ░░╚██╔╝░░░░░██║░░░╚════╝░██╔██╗░  ██║░░██╗██║░░██║██║╚████║██╔══╝░░██║██║░░╚██╗
#    ░░░██║░░░░░░██║░░░░░░░░░██╔╝╚██╗  ╚█████╔╝╚█████╔╝██║░╚███║██║░░░░░██║╚██████╔╝
#    ░░░╚═╝░░░░░░╚═╝░░░░░░░░░╚═╝░░╚═╝  ░╚════╝░░╚════╝░╚═╝░░╚══╝╚═╝░░░░░╚═╝░╚═════╝░
#

# loads the extension always
# useful for defining changes and overides to the default behaviour
# eg env,ui,functions
# all file names in the extensions folder
AUTO_LOADED_EXTENSIONS: $AUTO_LOADED_EXTENSIONS

# whether to show colors when printing ouput
PRETTY_PRINT: $PRETTY_PRINT

# your preferred editor for editing your config
EDITOR: $PREFERRED_EDITOR

# your preferred selector for the tui [fzf/rofi]
PREFERRED_SELECTOR: $PREFERRED_SELECTOR

# the quality of the video when streaming with a player other than mpv
VIDEO_QUALITY: $VIDEO_QUALITY

# whether to show previews [true/false]
# its cool so enable it
ENABLE_PREVIEW: $ENABLE_PREVIEW

# what to use for rendering images in the terminal [chafa/icat]
IMAGE_RENDERER: $IMAGE_RENDERER

# whether to run mpv as a background process and prevent it from closing even if you terminate the program or terminal session
DISOWN_STREAMING_PROCESS: $DISOWN_STREAMING_PROCESS

# whether to update the recent list kept locally [true/false]
UPDATE_RECENT: $UPDATE_RECENT

# whether to update the recent list kept locally [true/false]
SEARCH_HISTORY: $SEARCH_HISTORY

# the number of recent videos to keep
NO_OF_RECENT: $NO_OF_RECENT

# the player to use for streaming [mpv/vlc]
PLAYER: $PLAYER

# the browser to use to extract cookies from
# this is used to by yt-dlp to access content that would require login
PREFERRED_BROWSER: $(echo "$PREFERRED_BROWSER" | sed 's/--cookies-from-browser //g')

# the number of results to get from yt-dlp
NO_OF_SEARCH_RESULTS: $NO_OF_SEARCH_RESULTS

# the duration notifications stay on the screen
NOTIFICATION_DURATION: $NOTIFICATION_DURATION

# where your downloads will be stored
DOWNLOAD_DIRECTORY: $DOWNLOAD_DIRECTORY

# whether to check for updates [true/false]
UPDATE_CHECK: $UPDATE_CHECK

# whether to enable the welcome screeen which runs once a day [true/false]
WELCOME_SCREEN: $WELCOME_SCREEN
"
}

find_config_option() {
  local option=$1
  awk -F': ' "/^$option:/ && !/#/ {print \$2}" "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
}

load_config() {
  ! [ -f "$CLI_CONFIG_DIR/${CLI_NAME}.conf" ] && touch "$CLI_CONFIG_DIR/${CLI_NAME}.conf"

  PRETTY_PRINT="$(find_config_option "PRETTY_PRINT")"
  [ -z "$PRETTY_PRINT" ] && PRETTY_PRINT="true"

  IMAGE_RENDERER="$(find_config_option "IMAGE_RENDERER")"
  [ -z "$IMAGE_RENDERER" ] && IMAGE_RENDERER=$([ -n "$KITTY_WINDOW_ID" ] && echo "icat" || echo "chafa")

  DISOWN_STREAMING_PROCESS="$(find_config_option "DISOWN_STREAMING_PROCESS")"
  [ -z "$DISOWN_STREAMING_PROCESS" ] && DISOWN_STREAMING_PROCESS="true"

  PREFERRED_EDITOR="$(find_config_option "EDITOR")"
  [ -z "$PREFERRED_EDITOR" ] && PREFERRED_EDITOR=${EDITOR:-open}

  PREFERRED_SELECTOR="$(find_config_option "PREFERRED_SELECTOR")"
  [ -z "$PREFERRED_SELECTOR" ] && PREFERRED_SELECTOR="fzf"

  VIDEO_QUALITY="$(find_config_option "VIDEO_QUALITY")"
  [ -z "$VIDEO_QUALITY" ] && VIDEO_QUALITY=1080

  ENABLE_PREVIEW="$(find_config_option "ENABLE_PREVIEW")"
  [ -z "$ENABLE_PREVIEW" ] && ENABLE_PREVIEW="false"

  UPDATE_RECENT="$(find_config_option "UPDATE_RECENT")"
  [ -z "$UPDATE_RECENT" ] && UPDATE_RECENT="true"

  NO_OF_RECENT="$(find_config_option "NO_OF_RECENT")"
  [ -z "$NO_OF_RECENT" ] && NO_OF_RECENT=30

  PLAYER="$(find_config_option "PLAYER")"
  [ -z "$PLAYER" ] && PLAYER='mpv'

  PREFERRED_BROWSER="$(find_config_option "PREFERRED_BROWSER")"
  [ -n "$PREFERRED_BROWSER" ] && PREFERRED_BROWSER="--cookies-from-browser $PREFERRED_BROWSER"

  NO_OF_SEARCH_RESULTS="$(find_config_option "NO_OF_SEARCH_RESULTS")"
  [ -z "$NO_OF_SEARCH_RESULTS" ] && NO_OF_SEARCH_RESULTS=30

  NOTIFICATION_DURATION="$(find_config_option "NOTIFICATION_DURATION")"
  [ -z "$NOTIFICATION_DURATION" ] && NOTIFICATION_DURATION=5

  SEARCH_HISTORY="$(find_config_option "SEARCH_HISTORY")"
  [ -z "$SEARCH_HISTORY" ] && SEARCH_HISTORY="true"

  DOWNLOAD_DIRECTORY="$(find_config_option "DOWNLOAD_DIRECTORY")"
  DOWNLOAD_DIRECTORY=${DOWNLOAD_DIRECTORY/#\~/${HOME}}     # expand ~
  DOWNLOAD_DIRECTORY=${DOWNLOAD_DIRECTORY/#\$HOME/${HOME}} # expand $HOME
  [ -z "$DOWNLOAD_DIRECTORY" ] && DOWNLOAD_DIRECTORY="${XDG_VIDEOS_DIR:-"$HOME"/Videos}/$CLI_NAME"
  [ -d "$DOWNLOAD_DIRECTORY" ] || mkdir -p "$DOWNLOAD_DIRECTORY"

  UPDATE_CHECK="$(find_config_option "UPDATE_CHECK")"
  [ -z "$UPDATE_CHECK" ] && UPDATE_CHECK="true"

  WELCOME_SCREEN="$(find_config_option "WELCOME_SCREEN")"
  [ -z "$WELCOME_SCREEN" ] && WELCOME_SCREEN="true"

  ROFI_THEME="$(find_config_option "ROFI_THEME")"
  CUSTOM_PLAYLISTS="$CLI_CONFIG_DIR/custom_playlists.json"
  F_CUSTOM_CMDS="$CLI_CONFIG_DIR/custom_cmds.json"
  F_SUBSCRIPTIONS="$CLI_CONFIG_DIR/subscriptions.json"
  PLAYLIST_START="1"
  PLAYLIST_END="$NO_OF_SEARCH_RESULTS"

  FZF_DEFAULT_OPTS=${YT_X_FZF_OPTS:-'
    --color=fg:#d0d0d0,fg+:#d0d0d0,bg:#121212,bg+:#262626
    --color=hl:#5f87af,hl+:#5fd7ff,info:#afaf87,marker:#87ff00
    --color=prompt:#d7005f,spinner:#af5fff,pointer:#af5fff,header:#87afaf
    --color=border:#262626,label:#aeaeae,query:#d9d9d9
    --border="rounded" --border-label="" --preview-window="border-rounded" --prompt="> "
    --marker=">" --pointer="◆" --separator="─" --scrollbar="│"
  '}
  init_pretty_print
  AUTO_LOADED_EXTENSIONS="$(awk -F': ' '/^AUTO_LOADED_EXTENSIONS:/ && !/#/ {print $2}' "$CLI_CONFIG_DIR/${CLI_NAME}.conf")"
  if [ -n "$AUTO_LOADED_EXTENSIONS" ]; then
    for ext in $(echo "$AUTO_LOADED_EXTENSIONS" | tr ',' '\n'); do
      [ -s "$CLI_EXTENSION_DIR/$ext" ] && . "$CLI_EXTENSION_DIR/$ext"
    done
  fi
  if ! [ -s "$CLI_CONFIG_DIR/${CLI_NAME}.conf" ]; then
    print_config >"$CLI_CONFIG_DIR/${CLI_NAME}.conf"
  fi
  export FZF_DEFAULT_OPTS PRETTY_PRINT PLATFORM IMAGE_RENDERER
}
send_notification() {
  echo "$1" >&2 && sleep "$NOTIFICATION_DURATION"
}
update_script() {
  yt_x_path="$(command -v yt-x)"
  if [ -z "$yt_x_path" ]; then
    send_notification "Can't find yt-x in PATH"
    exit 1
  fi

  if [ ! -w "$yt_x_path" ]; then
    if [ -n "$(command -v sudo)" ]; then
      exec sudo -s "$yt_x_path" "-u"
    else
      send_notification "Insufficient permissions to update and can't find sudo in PATH"
      exit 1
    fi
  fi

  update=$(curl -s "https://raw.githubusercontent.com/Benexl/yt-x/refs/heads/master/yt-x" || byebye 1)
  update="$(printf '%s\n' "$update" | diff -u "$yt_x_path" - 2>/dev/null)"
  if [ -z "$update" ]; then
    send_notification "Script is up to date :)"
  else
    if printf '%s\n' "$update" | patch "$yt_x_path" -; then
      send_notification "Script has been updated!"
    else
      send_notification "Can't update for some reason!"
    fi
  fi
  exec "$yt_x_path"
}

check_update() {
  update=$(curl -s "https://raw.githubusercontent.com/Benexl/yt-x/refs/heads/master/yt-x")
  update="$(printf '%s\n' "$update" | diff -u "$(command -v yt-x)" - 2>/dev/null)"
  if [ -n "$update" ]; then
    confirm "An update has been found would you like to see the changes before deciding whether to update?" && echo "$update" | less
    if [ "$PREFERRED_SELECTOR" = "fzf" ]; then
      answer=$(prompt "$1")
    else
      answer=$(printf "Yes\nNo" | launcher "$1")
    fi
    case "$answer" in
    [Yy]*) update_script ;;
    esac
  fi
}

# Adapted from the preview script in the fzf repo
fzf_preview() {
  file=$1

  dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
  if [ "$dim" = x ]; then
    dim=$(stty size </dev/tty | awk "{print \$2 \"x\" \$1}")
  fi
  if ! [ "$IMAGE_RENDERER" = "icat" ] && [ -z "$KITTY_WINDOW_ID" ] && [ "$((FZF_PREVIEW_TOP + FZF_PREVIEW_LINES))" -eq "$(stty size </dev/tty | awk "{print \$1}")" ]; then
    dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
  fi

  if [ "$IMAGE_RENDERER" = "icat" ] && [ -z "$GHOSTTY_BIN_DIR" ]; then
    if command -v kitten >/dev/null 2>&1; then
      kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    elif command -v icat >/dev/null 2>&1; then
      icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    else
      kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    fi

  elif [ -n "$GHOSTTY_BIN_DIR" ]; then
    if command -v kitten >/dev/null 2>&1; then
      kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    elif command -v icat >/dev/null 2>&1; then
      icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed "\$d" | sed "$(printf "\$s/\$/\033[m/")"
    else
      chafa -s "$dim" "$file"
    fi
  elif command -v chafa >/dev/null 2>&1; then
    case "$PLATFORM" in
    android) chafa -s "$dim" "$file" ;;
    windows) chafa -f sixel -s "$dim" "$file" ;;
    *) chafa -s "$dim" "$file" ;;
    esac
    echo

  elif command -v imgcat >/dev/null; then
    imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"

  else
    echo please install a terminal image viewer
    echo either icat for kitty terminal and wezterm or imgcat or chafa
  fi
}

confirm() {
  if command -v "gum" >/dev/null 2>&1; then
    gum confirm "$1"
  else
    echo "$CLI_HEADER" >/dev/stderr
    printf "%s [y/N]: " "$1" >/dev/stderr
    read -r CONFIRMED
    case "$CONFIRMED" in
    y | Y)
      return 0
      ;;
    *)
      return 1
      ;;
    esac
  fi
}
launcher() {
  case "$(echo "$PREFERRED_SELECTOR" | tr '[:upper:]' '[:lower:]')" in
  rofi)
    if [ -z "$ROFI_THEME" ]; then
      rofi_selection=$(while read -r line; do echo "$line" | sed -r 's/\x1B(\[[0-9;]*[a-zA-Z]|\(B)//g'; done | rofi -sort -matching fuzzy -dmenu -i -width 1500 -p "" -mesg "Select Action" -matching fuzzy -sorting-method fzf)
      [ -z "$rofi_selection" ] && echo "Exit" || echo "$rofi_selection"
    else
      rofi_selection=$(while read -r line; do echo "$line" | sed -r 's/\x1B(\[[0-9;]*[a-zA-Z]|\(B)//g'; done | rofi -no-config -theme "$ROFI_THEME" -sort -matching fuzzy -dmenu -i -width 1500 -p "" -mesg "Select Action" -matching fuzzy -sorting-method fzf)
      [ -z "$rofi_selection" ] && echo "Exit" || echo "$rofi_selection"
    fi
    ;;
  *)
    fzf \
      --info=hidden \
      --layout=reverse \
      --height=100% \
      --prompt="${1}: " \
      --header-first --header="$CLI_HEADER" \
      --exact --cycle --ansi
    ;;
  esac

}

generate_sha256() {
  local input

  # Check if input is passed as an argument or piped
  if [ -n "$1" ]; then
    input="$1"
  else
    input=$(cat)
  fi

  if command -v sha256sum &>/dev/null; then
    echo -n "$input" | sha256sum | awk '{print $1}'
  elif command -v shasum &>/dev/null; then
    echo -n "$input" | shasum -a 256 | awk '{print $1}'
  elif command -v sha256 &>/dev/null; then
    echo -n "$input" | sha256 | awk '{print $1}'
  elif command -v openssl &>/dev/null; then
    echo -n "$input" | openssl dgst -sha256 | awk '{print $2}'
  else
    echo -n "$input" | base64 | tr '/+' '_-' | tr -d '\n'
  fi
}

generate_text_preview() {
  [ -z "$search_results" ] && return 1
  ids="$(echo "$1" | jq '.entries[].id' -r 2>/dev/null)"

  lines="$(echo "$ids" | wc -l)"
  for i in $(seq 1 "$lines"); do
    video=$(echo "$1" | jq ".entries[$((i - 1))]")
    title=$(echo "$video" | jq ".title" -r | sed 's/"/\\\\"/g;s/%/%%/g;s/\$/\\\\$/g;s/^.. //g')
    id=$(echo "$video" | jq '.id' -r)
    preview_image=$(echo "$video" | jq '.thumbnails[-1].url' -r | generate_sha256)

    view_count=$(
      echo "$video" | jq -r '
      .view_count
      |tostring
      |split("")
      |reverse
      |join("")
      |gsub("(?<thousands>[0-9]{3})(?=[0-9])"; "\(.thousands),")
      |split("")
      |reverse
      |join("")
      '
    )

    live_status=$(echo "$video" | jq '.live_status' -r)
    [ "$live_status" = "is_live" ] && live_status='Online'
    [ "$live_status" = "was_live" ] && live_status='Offline'
    [ "$live_status" = "null" ] && live_status='False'

    description=$(echo "$video" | jq '.description' -r | sed "s/\"//g;s/%//g")
    channel=$(echo "$video" | jq '.channel' -r)

    # some duration calculations
    duration=$(echo "$video" | jq '
    try 
      if .duration>=3600 then
        if .duration/3600|floor == 1 then 
          .duration/3600|floor|tostring + " hour" 
        else 
          .duration/3600|floor|tostring + " hours" 
        end 
      elif .duration>=60 then
        if .duration/60|floor == 1 then 
          .duration/60|floor|tostring + " min" 
        else 
          .duration/60|floor|tostring + " mins" 
        end 
      else 
        if .duration == 1 then 
          .duration/1|floor|tostring + " sec" 
        else 
          .duration/1|floor|tostring + " secs" 
        end 
      end
    catch
      "Unknown" 
    ' -r)

    # some date calculations
    timestamp=$(echo "$video" | jq '.timestamp' -r)
    relative_timestamp=$(("$CURRENT_TIME" - "$timestamp"))
    if [ "$relative_timestamp" -lt 60 ]; then
      timestamp="just now"
    elif [ "$relative_timestamp" -lt 3600 ]; then
      timestamp=$((relative_timestamp / 60))
      if [ "$timestamp" -eq 1 ]; then
        timestamp="$timestamp minute ago"
      else
        timestamp="$timestamp minutes ago"
      fi
    elif [ "$relative_timestamp" -lt 86400 ]; then
      timestamp=$((relative_timestamp / 3600))
      if [ "$relative_timestamp" -lt 7200 ]; then
        timestamp="1 hour ago"
      else
        timestamp=$((relative_timestamp / 3600))
        timestamp="$timestamp hours ago"
      fi
    elif [ "$relative_timestamp" -lt 604800 ]; then
      timestamp=$((relative_timestamp / 86400))
      if [ "$timestamp" -eq 1 ]; then
        timestamp="$timestamp day ago"
      else
        timestamp="$timestamp days ago"
      fi
    elif [ "$relative_timestamp" -lt 2635200 ]; then
      timestamp=$((relative_timestamp / 604800))
      if [ "$timestamp" -eq 1 ]; then
        timestamp="$timestamp week ago"
      else
        timestamp="$timestamp weeks ago"
      fi
    elif [ "$relative_timestamp" -lt 31622400 ]; then
      timestamp=$((relative_timestamp / 2635200))
      if [ "$timestamp" -eq 1 ]; then
        timestamp="$timestamp month ago"
      else
        timestamp="$timestamp months ago"
      fi
    else
      timestamp=$((relative_timestamp / 31622400))
      if [ "$timestamp" -eq 1 ]; then
        timestamp="$timestamp year ago"
      else
        timestamp="$timestamp years ago"
      fi
    fi

    printf "

if [ -f \"$CLI_PREVIEW_IMAGES_CACHE_DIR/${preview_image}.jpg\" ];then fzf_preview \"$CLI_PREVIEW_IMAGES_CACHE_DIR/${preview_image}.jpg\" 2>/dev/null;
else echo loading preview image...;
fi
ll=1
while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
echo

echo \"$title\"

ll=1
while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
printf \"${MAGENTA}${BOLD}Channel: ${RESET}$channel\n\";
printf \"${MAGENTA}${BOLD}Duration: ${RESET}$duration\n\";
printf \"${MAGENTA}${BOLD}View Count: ${RESET}$view_count views\n\";
printf \"${MAGENTA}${BOLD}Live Status: ${RESET}$live_status\n\";
printf \"${MAGENTA}${BOLD}Uploaded: ${RESET}$timestamp\n\";

ll=1
while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
echo


! [ \"$description\" = \"null\" ] && echo -n \"%s\";

" "$description" >"$CLI_PREVIEW_SCRIPTS_DIR/$(echo "$video" | jq ".title" -r | generate_sha256).txt"

  done
}

download_preview_images() {
  local url video filename

  echo generating preview in the background...
  generate_text_preview "$1" &

  ids="$(echo "$1" | jq '.entries[].id' -r 2>/dev/null)"
  urls="$(echo "$1" | jq '.entries[].thumbnails[-1].url' -r 2>/dev/null)"
  [ -z "$ids" ] && return 1
  [ -z "$urls" ] && return 1

  [ -f "$CLI_PREVIEW_IMAGES_CACHE_DIR/previews.txt" ] && rm "$CLI_PREVIEW_IMAGES_CACHE_DIR/previews.txt"
  lines="$(echo "$ids" | wc -l)"
  for i in $(seq 1 "$lines"); do
    video=$(echo "$1" | jq ".entries[$((i - 1))]")
    url=$(echo "$urls" | head -n $i | tail -n 1)
    filename=$(echo "$url" | generate_sha256)
    if ! [ -s "$CLI_PREVIEW_IMAGES_CACHE_DIR/${filename}.jpg" ]; then
      echo "url = \"${2}${url}\"" >>"$CLI_PREVIEW_IMAGES_CACHE_DIR/previews.txt"
      echo "output = \"$CLI_PREVIEW_IMAGES_CACHE_DIR/${filename}.jpg\"" >>"$CLI_PREVIEW_IMAGES_CACHE_DIR/previews.txt"
    fi
  done
  curl -s -K "$CLI_PREVIEW_IMAGES_CACHE_DIR/previews.txt" 2>/dev/null &
}
launcher_with_preview() {
  case "$PREFERRED_SELECTOR" in
  rofi)
    if [ -z "$ROFI_THEME" ]; then
      rofi_selection=$(while read -r line; do echo "$line" | sed -r 's/\x1B(\[[0-9;]*[a-zA-Z]|\(B)//g'; done | rofi -sort -matching fuzzy -dmenu -i -width 1500 -p "" -mesg "$1" -matching fuzzy -sorting-method fzf)
      [ -z "$rofi_selection" ] && echo "Exit" || echo "$rofi_selection"
    else
      rofi_selection=$(while read -r line; do echo "$line" | sed -r 's/\x1B(\[[0-9;]*[a-zA-Z]|\(B)//g'; done | rofi -no-config -theme "$ROFI_THEME" -sort -matching fuzzy -dmenu -i -p "" -mesg "Select Action" -matching fuzzy -sorting-method fzf)
      [ -z "$rofi_selection" ] && echo "Exit" || echo "$rofi_selection"
    fi
    ;;
  *)
    fzf \
      --info=hidden \
      --layout=reverse \
      --height=100% \
      --prompt="${1}: " \
      --header-first --header="$CLI_HEADER" \
      --preview-window=left,35%,wrap --bind=right:accept \
      --expect=shift-left,shift-right --tabstop=1 \
      --cycle --exact \
      --ansi --preview="$2"
    ;;
  esac

}
init_pretty_print() {
  if [ "$PRETTY_PRINT" = "true" ]; then
    RED=$(tput setaf 1)
    MAGENTA="\x1b[38;2;215;0;95m"
    CYAN=$(tput setaf 6)
    BOLD=$(tput bold)
    RESET=$(tput sgr0)
  fi
}

welcome() {
  printf "${CYAN}How are you ${USERNAME:-${USER:-User}} 🙂?
If you enjoy the project and want to support it, you can buy me a coffee at $CLI_SUPPORT_PROJECT_URL.
Would you like to open the support page? Select yes to continue — otherwise, enjoy your YouTube-from-the-terminal experience 😁.
You can disable this message by turning off the WELCOME_SCREEN option in the config. It only appears once a month.${RESET}\n"
  confirm && xdg-open "$CLI_SUPPORT_PROJECT_URL" || open "$CLI_SUPPORT_PROJECT_URL"
}

byebye() {
  clear
  echo "Have a good day $USER"
  exit "${1:-0}"
}
prompt() {
  HISTORY=$(tail -n 10 "$CLI_CACHE_DIR/search_history.txt" | grep -v '^\s*$' | tac | nl -w2 -s'. ')
  HISTORY_TEXT="Enter :<command> <query> for extended search\nFilter by upload date - :hour, :today, :week, :month, :year\nFilter by content - :video, :movie, :live\n Filter by features - :hd, :4k, :hdr, :subtitles, :360, :vr, :3d, :local\nSort videos by - :newest, :views, :rating\n-----------------------------\nSearch history:\n$HISTORY\n-----------------------------\n(Enter !<n> to select from history. Example: !1)\n"
  if [ "$PREFERRED_SELECTOR" = "rofi" ]; then
    if [ "$PROMPT_CONTEXT" = "Search" ] && [ "$SEARCH_HISTORY" = "true" ] && [ -n "$HISTORY" ]; then
      rofi -dmenu \
        -p "$1" \
        -mesg "$(printf "\nSearch history:\n%s\n(Enter !&lt;n&gt; to select from history. Example: !1)\n" "$HISTORY")" \
        <<<""
      PROMPT_CONTEXT=""
    else
      rofi -dmenu -p "$1: "
    fi
  elif command -v "gum" >/dev/null 2>&1; then
    if [ "$PROMPT_CONTEXT" = "Search" ] && [ "$SEARCH_HISTORY" = "true" ] && [ -n "$HISTORY" ]; then
      #Combine the YT-X header with the search history
      HEADER_WITH_HISTORY=$(echo -e "$CLI_HEADER\n$HISTORY_TEXT")
      gum input --header "$HEADER_WITH_HISTORY" --prompt "$1: " --value "$2"
      PROMPT_CONTEXT=""
    else
      gum input --header "$CLI_HEADER" --prompt "$1: " --value "$2"
    fi
  else
    echo "$CLI_HEADER" >/dev/stderr
    if [ "$PROMPT_CONTEXT" = "Search" ] && [ "$SEARCH_HISTORY" = "true" ] && [ -n "$HISTORY" ]; then
      echo -e "$HISTORY_TEXT" >/dev/stderr
      PROMPT_CONTEXT=""
    fi
    printf "%s: " "$1" >/dev/stderr
    read -r VAL
    echo "$VAL"
  fi
}
run_yt_dlp() {
  if command -v "gum" >/dev/null 2>&1; then
    gum spin --show-output -- yt-dlp "$1" -J --flat-playlist --extractor-args youtubetab:approximate_date --playlist-start "$PLAYLIST_START" --playlist-end "$PLAYLIST_END" $PREFERRED_BROWSER || send_notification "Failed to fetch data : ("
  else
    echo "Loading..." >/dev/stderr
    yt-dlp "$1" -J --flat-playlist --extractor-args youtubetab:approximate_date --playlist-start "$PLAYLIST_START" --playlist-end "$PLAYLIST_END" $PREFERRED_BROWSER || send_notification "Failed to fetch data : ("
  fi
}

core_dep_ch() {
  ! command -v "yt-dlp" >/dev/null 2>&1 && echo yt-dlp is not installed and is a core dep please install it to proceed && exit 1
  ! command -v "jq" >/dev/null 2>&1 && echo jq is not installed and is a core dep please install it to proceed && exit 1
  ! command -v "fzf" >/dev/null 2>&1 && echo fzf is not installed and is a core dep please install it to proceed && exit 1
}
trap byebye INT TERM
PREVIEW_SCRIPT_FOR_VIDEOS="

  title={}
  id=\$(echo {} | generate_sha256)
  if  [ -f \"$CLI_PREVIEW_SCRIPTS_DIR/\${id}.txt\" ];then
    . \"$CLI_PREVIEW_SCRIPTS_DIR/\${id}.txt\";
  else
    echo Loading Preview...
  fi
"
PREVIEW_SCRIPT_FOR_CHANNELS="

  MAGENTA='\x1b[38;2;215;0;95m'
  BOLD=\$(tput bold)
  RESET=\$(tput sgr0)

  if ! [ -z {} ] && ! [ {} = \"Back\" ] && ! [ {} = \"Exit\" ] && ! [ {} = \"Main Menu\" ];then
    channels_data=\$(cat \"\$CLI_CONFIG_DIR/subscriptions.json\")
    title=\"\$(echo {}|sed 's/\"/\\\\\\\"/g')\"
    video=\$(echo \"\$channels_data\" | jq -r \".entries | map(select(.title == \\\"\$title\\\" )) | .[0]\" 2>/dev/null)

    id=\$(echo \$video |jq '.thumbnails[-1].url' -r | generate_sha256);

    channel=\$(echo \"\$video\"| jq '.channel' -r);

    channel_follower_count=\$(echo \"\$video\"| jq -r '
    .channel_follower_count
    |tostring
    |split(\"\")
    |reverse
    |join(\"\")
    |gsub(\"(?<thousands>[0-9]\\{3})(?=[0-9])\"; \"\\(.thousands),\")
    |split(\"\")
    |reverse
    |join(\"\")
    '
    );
    
    description=\$(echo \"\$video\"| jq '.description' -r);
    
    if [ -f \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" ];then fzf_preview \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" 2>/dev/null;
    else echo loading preview image...;
    fi

    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
    # printf \"\${MAGENTA}\${BOLD}Id: \${RESET}\$id\n\";
    printf \"\${MAGENTA}\${BOLD}Channel: \${RESET}\$channel\n\";
    printf \"\${MAGENTA}\${BOLD}Follower Count: \${RESET}\$channel_follower_count followers\n\";

    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
    ! [ \"\$description\" = \"null\" ] && echo -e \"\$description\";
  else
    echo Loading...;
  fi;
"

PREVIEW_SCRIPT_FOR_CHANNELS_EXPLORER="

  MAGENTA='\x1b[38;2;215;0;95m'
  BOLD=\$(tput bold)
  RESET=\$(tput sgr0)

  if ! [ -z {} ] && ! [ {} = \"Back\" ] && ! [ {} = \"Exit\" ] && ! [ {} = \"Main Menu\" ];then
    # channels_data=\$(cat \"\$CLI_CONFIG_DIR/subscriptions.json\")
    title=\"\$(echo {}|sed 's/\"/\\\\\\\"/g')\"
    video=\$(echo \"\$channels_data\" | jq -r \".entries | map(select(.title == \\\"\$title\\\" )) | .[0]\" 2>/dev/null)

    id=\$(echo \$video | jq '.thumbnails[-1].url' -r | generate_sha256);   

    channel=\$(echo \"\$video\"| jq '.channel' -r);

    channel_follower_count=\$(echo \"\$video\"| jq -r '
    .channel_follower_count
    |tostring
    |split(\"\")
    |reverse
    |join(\"\")
    |gsub(\"(?<thousands>[0-9]\\{3})(?=[0-9])\"; \"\\(.thousands),\")
    |split(\"\")
    |reverse
    |join(\"\")
    '
    );
    
    description=\$(echo \"\$video\"| jq '.description' -r);
    
    if [ -f \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" ];then fzf_preview \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" 2>/dev/null;
    else echo loading preview image...;
    fi

    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
    # printf \"\${MAGENTA}\${BOLD}Id: \${RESET}\$id\n\";
    printf \"\${MAGENTA}\${BOLD}Channel: \${RESET}\$channel\n\";
    printf \"\${MAGENTA}\${BOLD}Follower Count: \${RESET}\$channel_follower_count followers\n\";

    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
    ! [ \"\$description\" = \"null\" ] && echo -e \"\$description\";
  else
    echo Loading...;
  fi;
"
PREVIEW_SCRIPT_FOR_PLAYLISTS_EXPLORER="

  MAGENTA='\x1b[38;2;215;0;95m'
  BOLD=\$(tput bold)
  RESET=\$(tput sgr0)

  if ! [ -z {} ] && ! [ {} = \"Back\" ] && ! [ {} = \"Exit\" ] && ! [ {} = \"Main Menu\" ];then
    # channels_data=\$(cat \"\$CLI_CONFIG_DIR/subscriptions.json\")
    title=\"\$(echo {}|sed 's/\"/\\\\\\\"/g')\"
    video=\$(echo \"\$playlist_results\" | jq -r \".entries | map(select(.title == \\\"\$title\\\" )) | .[0]\" 2>/dev/null)
    title=\"\$(echo \"\$title\"|sed 's/^.. //g')\"

    id=\$(echo \$video | jq '.thumbnails[-1].url' -r | generate_sha256);   

    
    if [ -f \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" ];then fzf_preview \"$CLI_PREVIEW_IMAGES_CACHE_DIR/\${id}.jpg\" 2>/dev/null;
    else echo loading preview image...;
    fi

    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
      echo \"\$title\";
    ll=1
    while [ \$ll -le \$FZF_PREVIEW_COLUMNS ];do echo -n -e \"─\" ;(( ll++ ));done;
  else
    echo Loading...;
  fi;
"

playlist_explorer() {
  SHELL="bash"
  DOWNLOAD_IMAGES=0
  while true; do
    [ "$DOWNLOAD_IMAGES" = 0 ] &&
      search_results=$(echo "$search_results" | jq "
          .entries=(.entries 
          | to_entries
          | map(.value.title=\"\(.key+1|tostring| if (.|length) < 2 then \"0\" + . else . end) \"+.value.title) 
          |map(.value))
          ") && ! [ "$ENABLE_PREVIEW" = "true" ] || ! [ "$PREFERRED_SELECTOR" = "fzf" ] && DOWNLOAD_IMAGES=1
    titles=$(echo "$search_results" | jq '.entries[].title' -r 2>/dev/null)
    [ "$ENABLE_PREVIEW" = "true" ] && [ "$PREFERRED_SELECTOR" = "fzf" ] && [ "$DOWNLOAD_IMAGES" = 0 ] && download_preview_images "$search_results" "" && DOWNLOAD_IMAGES=1
    export search_results SHELL
    if [ "$ENABLE_PREVIEW" = "true" ]; then
      title="$(printf "%s\nNext\nPrevious\n${CYAN}󰌍${RESET}  Back\n${CYAN}󰍜${RESET}  Main Menu\n${RED}󰈆${RESET}  Exit" "$titles" | launcher_with_preview "select video" "$PREVIEW_SCRIPT_FOR_VIDEOS" | tr -d '\n' | sed 's/^[^0-9]  //g')"
    else
      title="$(printf "%s\nNext\nPrevious\nBack\nMain Menu\nExit" "$titles" | launcher "select video" | tr -d '\n' | sed 's/^[^0-9]  //g')"
    fi
    clear
    title="$(echo "$title" | sed 's/"/\\"/g')"
    case "$title" in
    Next)
      PLAYLIST_START=$((PLAYLIST_START + "$NO_OF_SEARCH_RESULTS"))
      PLAYLIST_END=$((PLAYLIST_END + "$NO_OF_SEARCH_RESULTS"))
      search_results=$(run_yt_dlp "$url")
      DOWNLOAD_IMAGES=0
      continue
      ;;
    Previous)
      PLAYLIST_START=$((PLAYLIST_START - "$NO_OF_SEARCH_RESULTS"))
      [ $PLAYLIST_START -le 0 ] && PLAYLIST_START=1
      PLAYLIST_END=$((PLAYLIST_END - "$NO_OF_SEARCH_RESULTS"))
      [ $PLAYLIST_END -le "$NO_OF_SEARCH_RESULTS" ] && PLAYLIST_END="$NO_OF_SEARCH_RESULTS"
      search_results=$(run_yt_dlp "$url")
      DOWNLOAD_IMAGES=0
      continue
      ;;
    "Main Menu")
      break
      ;;
    Back | "")
      break
      ;;
    Exit)
      byebye
      ;;
    esac
    id=$(echo "$title" | sed -E 's/^([0-9]+) .*/\1/g')
    video="$(echo "$search_results" | jq ".entries[$((id - 1))]")"
    title=$(echo "$video" | jq '.title' -r | sed 's/^[0-9]\+ //g')

    while true; do
      media_action="$(printf "\
${CYAN}${RESET}  Watch
${CYAN}${RESET}  Play All
${CYAN}󰎆${RESET}  Listen
${CYAN}${RESET}  Listen To All
${CYAN}${RESET}  Mix
${CYAN}${RESET}  Save
${CYAN}󰧎${RESET}  UnSave
${CYAN}󰐒${RESET}  Save Playlist
${CYAN}󰵀${RESET}  Subscribe To Channel
${CYAN}󱑤${RESET}  Download
${CYAN}󰦗${RESET}  Download All
${CYAN}󱑤${RESET}  Download (Audio Only)
${CYAN}󰦗${RESET}  Download All (Audio Only)
${CYAN}${RESET}  Open in Browser
${CYAN}${RESET}  Toggle Enumerate Downloads
${CYAN}${RESET}  Shell
${CYAN}󰌍${RESET}  Back
${RED}󰈆${RESET}  Exit" | launcher "Select Media Action" | sed 's/.  //g')"
      clear
      case "$media_action" in
      "Play All")
        if echo "$url" | grep -q "list=RD" || echo "$urlForAll" | grep -q "list=RD" || [ "$(echo "$search_results" | jq 'has("uploader_url") | not')" = "true" ]; then
          if [ -n "$urlForAll" ]; then
            # Fix YouTube Mix URLs FIRST
            url="${url/&start_radio=0/}"
            urlForAll="${urlForAll/&start_radio=1/}"
            cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "$urlForAll").m3u8"
            if ! [ -s "$cached_playlist" ]; then
              _mix_data=$(yt-dlp "$urlForAll" --flat-playlist -J)
              [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
              echo '#EXTM3U' >>"$cached_playlist"

              lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
              for i in $(seq 1 "$lines"); do
                local _video
                local _title
                local _channel
                local _url
                _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
                _title=$(echo "$_video" | jq ".title" -r)
                _channel=$(echo "$_video" | jq '.channel' -r)
                _url=$(echo "$_video" | jq '.url' -r)
                echo "#EXTINF:-1,$_title" >>"$cached_playlist"
                echo "$_url" >>"$cached_playlist"
                # echo "#EXTALB:Album Name" >>"$cached_playlist"
                # echo "#EXTGENRE:Genre" >>"$cached_playlist"
                # echo "#EXTGRP:Group Name" >>"$cached_playlist"
                echo "" >>"$cached_playlist"
              done
            fi
            mpv "$cached_playlist"
          else
            cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "$url").m3u8"

            if ! [ -s "$cached_playlist" ]; then
              _mix_data=$(yt-dlp "$url" --flat-playlist -J)
              [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
              echo '#EXTM3U' >>"$cached_playlist"

              lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
              for i in $(seq 1 "$lines"); do
                local _video
                local _title
                local _channel
                local _url
                _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
                _title=$(echo "$_video" | jq ".title" -r)
                _channel=$(echo "$_video" | jq '.channel' -r)
                _url=$(echo "$_video" | jq '.url' -r)
                echo "#EXTINF:-1,$_title" >>"$cached_playlist"
                echo "$_url" >>"$cached_playlist"
                # echo "#EXTALB:Album Name" >>"$cached_playlist"
                # echo "#EXTGENRE:Genre" >>"$cached_playlist"
                # echo "#EXTGRP:Group Name" >>"$cached_playlist"
                echo "" >>"$cached_playlist"
              done
            fi
            mpv "$cached_playlist"
          fi
        else
          if [ -n "$urlForAll" ]; then
            mpv "$urlForAll"
          else
            mpv "$url"
          fi
        fi
        ;;
      "Listen To All")
        if echo "$url" | grep -q "list=RD" || echo "$urlForAll" | grep -q "list=RD" || [ "$(echo "$search_results" | jq 'has("uploader_url") | not')" = "true" ]; then
          if [ -n "$urlForAll" ]; then
            cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "$urlForAll").m3u8"

            if ! [ -s "$cached_playlist" ]; then
              _mix_data=$(yt-dlp "$urlForAll" --flat-playlist -J)
              [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
              echo '#EXTM3U' >>"$cached_playlist"

              lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
              for i in $(seq 1 "$lines"); do
                local _video
                local _title
                local _channel
                local _url
                _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
                _title=$(echo "$_video" | jq ".title" -r)
                _channel=$(echo "$_video" | jq '.channel' -r)
                _url=$(echo "$_video" | jq '.url' -r)
                echo "#EXTINF:-1,$_title" >>"$cached_playlist"
                echo "$_url" >>"$cached_playlist"
                # echo "#EXTALB:Album Name" >>"$cached_playlist"
                # echo "#EXTGENRE:Genre" >>"$cached_playlist"
                # echo "#EXTGRP:Group Name" >>"$cached_playlist"
                echo "" >>"$cached_playlist"
              done
            fi
            mpv "$cached_playlist" --no-video --force-window=no
          else
            cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "$url").m3u8"
            if ! [ -s "$cached_playlist" ]; then
              _mix_data=$(yt-dlp "$url" --flat-playlist -J)
              [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
              echo '#EXTM3U' >>"$cached_playlist"

              lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
              for i in $(seq 1 "$lines"); do
                local _video
                local _title
                local _channel
                local _url
                _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
                _title=$(echo "$_video" | jq ".title" -r)
                _channel=$(echo "$_video" | jq '.channel' -r)
                _url=$(echo "$_video" | jq '.url' -r)
                echo "#EXTINF:-1,$_title" >>"$cached_playlist"
                echo "$_url" >>"$cached_playlist"
                # echo "#EXTALB:Album Name" >>"$cached_playlist"
                # echo "#EXTGENRE:Genre" >>"$cached_playlist"
                # echo "#EXTGRP:Group Name" >>"$cached_playlist"
                echo "" >>"$cached_playlist"
              done
            fi
            mpv "$cached_playlist" --no-video --force-window=no
          fi
        else
          if [ -n "$urlForAll" ]; then
            mpv "$urlForAll" --no-video --force-window=no
          else
            mpv "$url" --no-video --force-window=no
          fi
        fi
        ;;
      "Toggle Enumerate Downloads")
        if [ "$enumerate_playlist" = "" ]; then
          enumerate_playlist="%(playlist_index)s - "
        else
          enumerate_playlist=""
        fi
        ;;
      "Download All")
        playlist_name=$(prompt "Name of the playlist" "$playlist_title")
        if [ -n "$urlForAll" ]; then
          yt_dlp_cmd=("yt-dlp" "$urlForAll" "--output" "$DOWNLOAD_DIRECTORY/videos/$playlist_name/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" $PREFERRED_BROWSER)
          "${yt_dlp_cmd[@]}" --download-archive "$CLI_YT_DLP_ARCHIVE/$(echo -n -- "${yt_dlp_cmd[@]}" | generate_sha256)"
        else
          yt_dlp_cmd=("yt-dlp" "$url" "--output" "$DOWNLOAD_DIRECTORY/videos/$playlist_name/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" $PREFERRED_BROWSER)
          "${yt_dlp_cmd[@]}" --download-archive "$CLI_YT_DLP_ARCHIVE/$(echo -n -- "${yt_dlp_cmd[@]}" | generate_sha256)"
        fi
        send_notification "Completed downloading of $playlist_name"
        ;;
      "Download All (Audio Only)")
        playlist_name=$(prompt "Name of the playlist" "$playlist_title")

        if [ -n "$urlForAll" ]; then
          yt_dlp_cmd=("yt-dlp" "$urlForAll" "--audio-format" "mp3" '-x' '-f' 'bestaudio/best' "--output" "$DOWNLOAD_DIRECTORY/audio/$playlist_name/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" $PREFERRED_BROWSER)
          "${yt_dlp_cmd[@]}" --download-archive "$CLI_YT_DLP_ARCHIVE/$(echo -n -- "${yt_dlp_cmd[@]}" | generate_sha256)"
        else
          yt_dlp_cmd=("yt-dlp" "$url" "--audio-format" "mp3" '-x' '-f' 'bestaudio/best' "--output" "$DOWNLOAD_DIRECTORY/audio/$playlist_name/%(channel)s/$enumerate_playlist%(title)s.%(ext)s" $PREFERRED_BROWSER)
          "${yt_dlp_cmd[@]}" --download-archive "$CLI_YT_DLP_ARCHIVE/$(echo -n -- "${yt_dlp_cmd[@]}" | generate_sha256)"
        fi
        send_notification "Completed downloading of $playlist_name"
        ;;
      Listen)
        printf "${MAGENTA}Now Listening to:${RESET} $title\n"
        video_url=$(echo "$video" | jq '.url' -r)
        if echo "$video_url" | grep -q "list=RD"; then
          video_id=$(echo "$video" | jq '.id' -r | sed 's/RD//g')
          cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id").m3u8"
          if ! [ -s "$cached_playlist" ]; then
            _mix_data=$(yt-dlp "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id" --flat-playlist -J)
            [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
            echo '#EXTM3U' >>"$cached_playlist"

            lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
            for i in $(seq 1 "$lines"); do
              local _video
              local _title
              local _channel
              local _url
              _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
              _title=$(echo "$_video" | jq ".title" -r)
              _channel=$(echo "$_video" | jq '.channel' -r)
              _url=$(echo "$_video" | jq '.url' -r)
              echo "#EXTINF:-1,$_title" >>"$cached_playlist"
              echo "$_url" >>"$cached_playlist"
              # echo "#EXTALB:Album Name" >>"$cached_playlist"
              # echo "#EXTGENRE:Genre" >>"$cached_playlist"
              # echo "#EXTGRP:Group Name" >>"$cached_playlist"
              echo "" >>"$cached_playlist"
            done
          fi
          mpv "$cached_playlist" --no-video --force-window=no
        else
          if ! [ "$PLAYER" = mpv ] || [ "$PLATFORM" = android ]; then
            video_url=$(yt-dlp "$video_url" -q --no-warnings --get-url --format "bestaudio/best[height<=$VIDEO_QUALITY]/best" 2>/dev/null | tail -n 1)
            if ! [ "$?" = 0 ] || [ -z "$video_url" ]; then
              echo No video format found
              sleep 5
              break
            fi
          fi
          case "$PLATFORM" in
          android)
            case "$PLAYER" in
            mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;;
            vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "$title" >/dev/null 2>&1 & ;;
            esac
            ;;
          *)
            case "$PLAYER" in
            mpv)
              mpv "$video_url" --no-video --force-window=no
              ;;
            vlc) vlc "$video_url" --video-title "$title" ;;
            esac
            ;;
          esac

        fi

        current_recent_videos='{"entries":[]}'
        [ -s "$CLI_CONFIG_DIR/recent.json" ] && current_recent_videos=$(cat "$CLI_CONFIG_DIR/recent.json")
        id=$(echo "$video" | jq '.id' -r)
        echo "$current_recent_videos" | jq "{\"entries\":[.entries[] | select(.id != \"$id\")]}|.entries+=[$(echo "$video" | jq '.title |= sub("^[0-9]+ "; "")')]" | jq "{\"entries\":.entries[-$NO_OF_RECENT:]}" >"$CLI_CONFIG_DIR/recent.json"
        ;;
      Mix)
        local url0 _search_results
        video_id=$(echo "$video" | jq '.id' -r)
        url0=$urlForAll
        urlForAll="https://www.youtube.com/watch?v=${video_id}&list=RD$video_id"

        _search_results="$search_results"
        search_results=$(
          if command -v "gum" >/dev/null 2>&1; then
            gum spin --show-output -- yt-dlp "$urlForAll" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER $PLAYLISTS_EXTRA_ARGS --playlist-start 1 --playlist-end $NO_OF_SEARCH_RESULTS
          else
            echo "Loading..." >/dev/stderr
            yt-dlp "$urlForAll" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER $PLAYLISTS_EXTRA_ARGS --playlist-start 1 --playlist-end $NO_OF_SEARCH_RESULTS
          fi
        )
        playlist_explorer
        urlForAll=$url0
        search_results="$_search_results"
        ;;
      Watch)
        printf "${MAGENTA}Now watching:${RESET} $title\n"

        video_url=$(echo "$video" | jq '.url' -r)
        if echo "$video_url" | grep -q "list=RD"; then
          local _mix_data
          video_id=$(echo "$video" | jq '.id' -r | sed 's/RD//g')

          cached_playlist="$CLI_AUTO_GEN_PLAYLISTS/$(generate_sha256 "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id").m3u8"

          if ! [ -s "$cached_playlist" ]; then
            _mix_data=$(yt-dlp "https://www.youtube.com/watch?v=${video_id}&list=RD$video_id" --flat-playlist -J)
            [ -z "$_mix_data" ] && send_notification "Failed to get mix data" && continue
            echo '#EXTM3U' >>"$cached_playlist"

            lines="$(echo "$_mix_data" | jq '.entries[].url' -r | wc -l)"
            for i in $(seq 1 "$lines"); do
              local _video
              local _title
              local _channel
              local _url
              _video=$(echo "$_mix_data" | jq ".entries[$((i - 1))]")
              _title=$(echo "$_video" | jq ".title" -r)
              _channel=$(echo "$_video" | jq '.channel' -r)
              _url=$(echo "$_video" | jq '.url' -r)
              echo "#EXTINF:-1,$_title" >>"$cached_playlist"
              echo "$_url" >>"$cached_playlist"
              # echo "#EXTALB:Album Name" >>"$cached_playlist"
              # echo "#EXTGENRE:Genre" >>"$cached_playlist"
              # echo "#EXTGRP:Group Name" >>"$cached_playlist"
              echo "" >>"$cached_playlist"
            done
          fi
          mpv "$cached_playlist"
        else
          if ! [ "$PLAYER" = mpv ] || [ "$PLATFORM" = android ]; then
            video_url=$(yt-dlp "$video_url" -q --no-warnings --get-url --format "best[height<=$VIDEO_QUALITY]/best" 2>/dev/null | tail -n 1)
            if ! [ "$?" = 0 ] || [ -z "$video_url" ]; then
              echo No video format found
              sleep 5
              break
            fi
          fi
          case "$PLATFORM" in
          android)
            case "$PLAYER" in
            mpv) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n is.xyz.mpv/.MPVActivity >/dev/null 2>&1 & ;;
            vlc) nohup am start --user 0 -a android.intent.action.VIEW -d "$video_url" -n org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity -e "title" "$title" >/dev/null 2>&1 & ;;
            esac
            ;;
          *)
            case "$PLAYER" in
            mpv)
              mpv "$video_url"
              ;;
            vlc) vlc "$video_url" --video-title "$title" ;;
            esac
            ;;
          esac

        fi

        current_recent_videos='{"entries":[]}'
        [ -s "$CLI_CONFIG_DIR/recent.json" ] && current_recent_videos=$(cat "$CLI_CONFIG_DIR/recent.json")
        id=$(echo "$video" | jq '.id' -r)
        echo "$current_recent_videos" | jq "{\"entries\":[.entries[] | select(.id != \"$id\")]}|.entries+=[$(echo "$video" | jq '.title |= sub("^[0-9]+ "; "")')]" | jq "{\"entries\":.entries[-$NO_OF_RECENT:]}" >"$CLI_CONFIG_DIR/recent.json"
        ;;
      Save)
        # For now a pseudo like feature is just as useful
        current_liked_videos='{"entries":[]}'
        [ -s "$CLI_CONFIG_DIR/saved_videos.json" ] && current_liked_videos=$(cat "$CLI_CONFIG_DIR/saved_videos.json")
        id=$(echo "$video" | jq '.id' -r)
        echo "$current_liked_videos" | jq "{\"entries\":[.entries[] | select(.id != \"$id\")]}|.entries+=[$(echo "$video" | jq '.title |= sub("^[0-9]+ "; "")')]" >"$CLI_CONFIG_DIR/saved_videos.json"
        ;;
      Save\ Playlist)
        # For now a pseudo like feature is just as useful
        custom_playlists="[]"
        [ -s "$CLI_CONFIG_DIR/custom_playlists.json" ] && custom_playlists=$(cat "$CLI_CONFIG_DIR/custom_playlists.json")
        playlist_name=$(prompt "Enter the name of the playlist" "$playlist_title")
        if [ -n "$urlForAll" ]; then
          playlist_id=$(echo "$urlForAll" | sed 's/.*list=//g')
        else
          playlist_id=$(echo "$url" | sed 's/.*list=//g')
        fi
        custom_playlist="
          {
            \"name\": \"$playlist_name\",
            \"playlistUrl\": \"https://www.youtube.com/playlist?list=$playlist_id\",
            \"playlistWatchUrl\": \"https://www.youtube.com/watch?list=$playlist_id\"
          }
        "
        echo "$custom_playlists" | jq ".+=[$custom_playlist]" >"$CLI_CONFIG_DIR/custom_playlists.json" && send_notification "successfully added to custom playlists" || send_notification "Failed to add to custom playlists"
        ;;
      UnSave)
        current_liked_videos='{"entries":[]}'
        [ -s "$CLI_CONFIG_DIR/saved_videos.json" ] && current_liked_videos=$(cat "$CLI_CONFIG_DIR/saved_videos.json")
        id=$(echo "$video" | jq '.id' -r)
        echo "$current_liked_videos" | jq "{\"entries\":[.entries[] | select(.id != \"$id\")]}" >"$CLI_CONFIG_DIR/saved_videos.json"
        ;;
      "Subscribe To Channel")
        # TODO: use youtube api to enable video subscriptions
        send_notification "contribute to the project by adding this feature"
        sleep "$NOTIFICATION_DURATION"
        ;;
      "Visit Channel")
        # TODO: use youtube api to enable video subscriptions
        send_notification "contribute to the project by adding this feature"
        sleep "$NOTIFICATION_DURATION"
        ;;
      "Open in Browser")
        if command -v "open" >/dev/null 2>&1; then
          open "$(echo "$video" | jq '.url' -r)"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "$(echo "$video" | jq '.url' -r)"
        else
          send_notification "Could not find xdg-open or open"
        fi
        ;;
      Download)
        video_url=$(echo "$video" | jq '.url' -r)
        yt-dlp "$video_url" --output "$DOWNLOAD_DIRECTORY/videos/individual/%(channel)s/%(title)s.%(ext)s" $PREFERRED_BROWSER
        send_notification "Completed downloading of $title"
        ;;
      "Download (Audio Only)")
        video_url=$(echo "$video" | jq '.url' -r)
        yt-dlp "$video_url" -x -f 'bestaudio' --audio-format mp3 --output "$DOWNLOAD_DIRECTORY/audio/individual/%(channel)s/%(title)s.%(ext)s" $PREFERRED_BROWSER
        send_notification "Completed downloading of $title"
        ;;
      Back | "")
        break
        ;;
      Shell)
        video_url=$(echo "$video" | jq '.url' -r)
        export url urlForAll search_results video video_url playlist_title CLI_HEADER CLI_NAME DOWNLOAD_DIRECTORY CLI_YT_DLP_ARCHIVE
        local init_text="\
$CLI_HEADER
Welcome to the $CLI_NAME shell.
You can use the following:
 variables:
    - url
    - urlForAll
    - search_results
    - video
    - video_url
    - playlist_title
    - DOWNLOAD_DIRECTORY
    - CLI_YT_DLP_ARCHIVE
  functions:
    - generate_sha256
"
        user_shell="$(ps -o comm= -p "$PPID")"
        if [ "$user_shell" = "fish" ]; then
          fish --init-command "function fish_greeting; clear;echo \"$init_text\"; end"
        else
          bash --rcfile <(echo "[ -f ~/.bashrc ] && . ~/.bashrc;clear;echo \"$init_text\"")
        fi

        ;;
      Exit)
        byebye
        ;;
      *)
        echo invalid action
        sleep "$NOTIFICATION_DURATION"
        ;;
      esac
      clear
    done
  done
  unset urlForAll
  unset search_results
  PLAYLIST_START="1"
  PLAYLIST_END="$NO_OF_SEARCH_RESULTS"
}
get_channels_data() {
  [ "$FORCE_CHANNEL_THUMBNAILS_DOWNLOAD" = "0" ] && download_preview_images "$channels_data" "https:"
  if [ -f "$CLI_CONFIG_DIR/subscriptions.json" ] && ! [ "$force_update" = "1" ]; then
    channels_data=$(cat "$CLI_CONFIG_DIR/subscriptions.json")
  else
    echo Loading subscriptions...
    [ -n "$PREFERRED_BROWSER" ] && channels_data=$(yt-dlp "https://www.youtube.com/feed/channels" --flat-playlist $PREFERRED_BROWSER -J) || send_notification "Failed to fetch subscriptions (please set preferred browser in config)"
    ! [ "$FORCE_CHANNEL_THUMBNAILS_DOWNLOAD" = "0" ] && [ "$ENABLE_PREVIEW" = "true" ] && download_preview_images "$channels_data" "https:"
    [ -n "$channels_data" ] && ! [ "$channels_data" = "null" ] && echo "$channels_data" >"$CLI_CONFIG_DIR/subscriptions.json" || send_notification "Failed to fetch subscriptions"
    clear
  fi
}
playlists_explorer() {
  playlist_results=$(
    if command -v "gum" >/dev/null 2>&1; then
      gum spin --show-output -- yt-dlp "$url" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER $PLAYLISTS_EXTRA_ARGS
    else
      echo "Loading..." >/dev/stderr
      yt-dlp "$url" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER $PLAYLISTS_EXTRA_ARGS
    fi
  )
  playlist_results=$(echo "$playlist_results" | jq "
          .entries=(.entries 
          | to_entries
          | map(.value.title=\"\(.key+1|tostring| if (.|length) < 2 then \"0\" + . else . end) \"+.value.title) 
          |map(.value))
          ")
  playlist_video_titles=$(echo "$playlist_results" | jq '.entries[].title' -r 2>/dev/null)
  [ "$ENABLE_PREVIEW" = "true" ] && [ "$PREFERRED_SELECTOR" = "fzf" ] && download_preview_images "$playlist_results" ""
  export playlist_results SHELL
  while true; do
    if [ "$ENABLE_PREVIEW" = "true" ]; then

      playlist_title="$(printf "%s\n${CYAN}󰌍${RESET}  Back\n${RED}󰈆${RESET}  Exit" "$playlist_video_titles" | launcher_with_preview "select video" "$PREVIEW_SCRIPT_FOR_PLAYLISTS_EXPLORER" | tr -d '\n' | sed 's/^[^0-9]  //g')"
    else
      playlist_title="$(printf "%s\nBack\nExit" "$playlist_video_titles" | launcher "select video" | tr -d '\n' | sed 's/^[^0-9]  //g')"
    fi
    clear
    playlist_title="$(echo "$playlist_title" | sed 's/"/\\"/g')"
    case "$playlist_title" in
    *Back | "")
      break
      ;;
    *Exit)
      byebye
      ;;
    esac
    playlist_title="$(echo "$playlist_title" | sed 's/"/\\"/g;s/ *$//g' | tr -d "\n")"
    playlist="$(echo "$playlist_results" | jq ".entries|map(select(.title == \"$playlist_title\"))|.[0]")"
    playlist_title="$(echo "$playlist_title" | sed 's/^.. //g')"
    url="$(echo "$playlist" | jq '.url' -r)"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
  done
}
channels_explorer() {
  while true; do
    channel_action="$(printf "\
${CYAN}${RESET}  Videos
${CYAN}󰩉${RESET}  Featured
${CYAN}${RESET}  Search
${CYAN}󰐑${RESET}  Playlists
${CYAN}${RESET}  Shorts
${CYAN}󰠿${RESET}  Streams
${CYAN}${RESET}  Podcasts
${CYAN}󰵀${RESET}  Subscribe
${CYAN}󰌍${RESET}  Back
${RED}󰈆${RESET}  Exit
" | launcher "Select Action" | sed 's/.  //g')"
    [ "$channel_action" = "Exit" ] && byebye
    [ "$channel_action" = "Back" ] || [ "$channel_action" = "" ] && break
    uploader_url_base="$(echo "$channel" | jq '.uploader_url' -r)"

    case "$channel_action" in
    Videos)
      url="$uploader_url_base/videos"
      search_results=$(run_yt_dlp "$url")
      playlist_explorer
      ;;
    Streams)
      url="$uploader_url_base/streams"
      search_results=$(run_yt_dlp "$url")
      playlist_explorer
      ;;
    Podcasts)
      url="$uploader_url_base/podcasts"
      playlists_explorer
      ;;
    Shorts)
      url="$uploader_url_base/shorts"
      search_results=$(run_yt_dlp "$url")
      playlist_explorer
      ;;
    Featured)
      url="$uploader_url_base/featured"
      playlists_explorer
      ;;
    Playlists)
      url="$uploader_url_base/playlists"
      playlists_explorer
      ;;
    Search)
      clear
      search_term="$(prompt "Enter term to search for" | jq -Rr '@uri')"
      url="$uploader_url_base/search?query=$search_term"
      search_results=$(run_yt_dlp "$url")
      playlist_explorer
      ;;
    Subscribe)
      if ! [ -s "$CLI_CONFIG_DIR/subscriptions.json" ]; then
        if confirm "Would you like to import your youtube subscriptions first? You wont be able to do so again unless you delete subscriptions.json"; then
          _channels_data=$(yt-dlp "https://www.youtube.com/feed/channels" --flat-playlist $PREFERRED_BROWSER -J)
          echo "$_channels_data" >"$CLI_CONFIG_DIR/subscriptions.json"
        else

          _channels_data='{"entries":[]}'
        fi
      else
        _channels_data=$(cat "$CLI_CONFIG_DIR/subscriptions.json")
      fi

      id=$(echo "$channel" | jq '.id' -r)
      echo "$_channels_data" | jq "{\"entries\":[.entries[] | select(.id != \"$id\")]}|.entries+=[$channel]" >"$CLI_CONFIG_DIR/subscriptions.json" && send_notification "successfully subscribed" || send_notification "Failed to subscribe to channel"
      ;;
    Exit)
      byebye
      ;;
    *)
      echo invalid channel action
      sleep "$NOTIFICATION_DURATION"
      ;;
    esac
    clear
  done

}
main() {
  SHELL="bash"
  clear
  case "${CMD_ACTION}" in
  Search)
    unset CMD_ACTION
    action="Search"
    ;;
  *)
    action="$(printf "\
${CYAN}${RESET}  Your Feed
${CYAN}${RESET}  Trending
${CYAN}󰐑${RESET}  Playlists
${CYAN}${RESET}  Search
${CYAN}${RESET}  Watch Later
${CYAN}󰵀${RESET}  Subscription Feed
${CYAN}󰑈${RESET}  Channels
${CYAN}${RESET}  Custom Playlists
${CYAN}${RESET}  Liked Videos
${CYAN}${RESET}  Saved Videos
${CYAN}${RESET}  Watch History
${CYAN}${RESET}  Recent
${CYAN}${RESET}  Clips
${CYAN}${RESET}  Edit Config
${CYAN}${RESET}  Miscellaneous
${RED}󰈆${RESET}  Exit" | launcher "Select Action" | sed 's/.*  //g')"
    ;;
  esac
  [ "$action" = "Exit" ] && byebye

  unset urlForAll
  case "$action" in
  "Your Feed")
    url="https://www.youtube.com"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  Trending)
    url="https://www.youtube.com/feed/trending"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  Search)
    clear
    PROMPT_CONTEXT="Search"
    if [ -z "$CMD_SEARCH_TERMS" ]; then
      search_term="$(prompt "Enter term to search for")"
      if [[ "$search_term" =~ ^![0-9]{1,2}$ ]]; then
        index="${search_term:1}" # remove the leading "!"
        history_item=$(tail -n 10 "$CLI_CACHE_DIR/search_history.txt" | tac | sed -n "${index}p")
        if [[ -n "$history_item" ]]; then
          echo "Using history item #$index: $history_item"
          search_term="$history_item"
        else
          echo "No such history item: $index"
        fi
      fi
      # Exit if user presses ESC or leaves search empty in rofi
      if [ "$PREFERRED_SELECTOR" = "rofi" ] && [ -z "$search_term" ]; then
        echo "Search cancelled. No search term provided."
        return 1
      fi
    else
      search_term="$CMD_SEARCH_TERMS"
      unset CMD_SEARCH_TERMS
    fi
    if [[ "$search_term" =~ ^(:[a-z]+)[[:space:]]+(.+) ]]; then
      search_filter="${BASH_REMATCH[1]}"
      search_term="${BASH_REMATCH[2]}"
      case "$search_filter" in
      ":hour") sp="EgIIAQ%253D%253D" ;;
      ":today") sp="EgIIAg%253D%253D" ;;
      ":week") sp="EgIIAw%253D%253D" ;;
      ":month") sp="EgIIBA%253D%253D" ;;
      ":year") sp="EgIIBQ%253D%253D" ;;
      ":video") sp="EgIQAQ%253D%253D" ;;
      ":movie") sp="EgIQBA%253D%253D" ;;
      ":live") sp="EgJAAQ%253D%253D" ;;
      ":short") sp="EgQQARgB" ;;
      ":long") sp="EgQQARgC" ;;
      ":4k") sp="EgJwAQ%253D%253D" ;;
      ":hd") sp="EgIgAQ%253D%253D" ;;
      ":subtitles") sp="EgIoAQ%253D%253D" ;;
      ":360") sp="EgJ4AQ%253D%253D" ;;
      ":vr") sp="EgLIAQ%253D%253D" ;;
      ":3d") sp="EgI4AQ%253D%253D" ;;
      ":hdr") sp="EgPIAQ%253D%253D" ;;
      ":local") sp="EgO4AQ%253D%253D" ;;
      ":newest") sp="CAISAhAB" ;;
      ":views") sp="CAMSAhAB" ;;
      ":rating") sp="CAESAhAB" ;;
      *) sp="EgIQAQ%253D%253D" ;;
      esac
    fi
    [ "$SEARCH_HISTORY" = "true" ] && [ -s "$CLI_CACHE_DIR/search_history.txt" ] && history=$(grep --invert-match "^$search_term\$" "$CLI_CACHE_DIR/search_history.txt")
    [ "$SEARCH_HISTORY" = "true" ] && [ -s "$CLI_CACHE_DIR/search_history.txt" ] && echo "$history" >$CLI_CACHE_DIR/search_history.txt
    [ "$SEARCH_HISTORY" = "true" ] && echo "$search_term" >>"$CLI_CACHE_DIR/search_history.txt"
    search_term=$(echo "$search_term" | jq -Rr '@uri')
    url="https://www.youtube.com/results?search_query=$search_term&sp=$sp"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  "Subscription Feed")
    url="https://www.youtube.com/feed/subscriptions"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  Playlists)
    url="https://www.youtube.com/feed/playlists"
    playlists_explorer
    ;;
  "Saved Videos")
    ! [ -s "$CLI_CONFIG_DIR/saved_videos.json" ] && echo No saved videos && sleep "$NOTIFICATION_DURATION" && main
    search_results=$(jq '{"entries":.entries|reverse}' "$CLI_CONFIG_DIR/saved_videos.json")
    playlist_explorer
    ;;
  Recent)
    ! [ -s "$CLI_CONFIG_DIR/recent.json" ] && echo No recent videos && sleep "$NOTIFICATION_DURATION" && main
    search_results=$(jq '{"entries":.entries|reverse}' "$CLI_CONFIG_DIR/recent.json")
    playlist_explorer
    ;;
  Channels)
    while true; do
      get_channels_data
      channels=$(echo "$channels_data" | jq '.entries[].channel' -r)
      export SHELL CLI_CONFIG_DIR
      channel_name=$(printf "%s\nMain Menu\nExit" "$channels" | launcher_with_preview "Select Channel" "$PREVIEW_SCRIPT_FOR_CHANNELS")
      channel_name="$(echo "$channel_name" | sed 's/"/\\"/g;s/ *$//g' | tr -d "\n")"
      [ "$channel_name" = "Exit" ] && byebye
      [ "$channel_name" = "Main Menu" ] && break
      channel="$(echo "$channels_data" | jq ".entries|map(select(.channel == \"$channel_name\"))|.[0]")"
      channels_explorer
    done
    ;;
  Custom\ Playlists)
    ! [ -s "$CUSTOM_PLAYLISTS" ] && echo "You don't have any custom playlists. Create them here: <$CUSTOM_PLAYLISTS>" && sleep "$NOTIFICATION_DURATION" && main
    while true; do
      playlist_title=$(printf "%s\nBack" "$(jq -r '.|reverse|.[].name' "$CUSTOM_PLAYLISTS")" | launcher "Select Custom Playlist To Play")
      [ "$playlist_title" = "Back" ] || [ "$playlist_title" = "" ] && break
      url=$(jq -r ". | map(select(.name == \"$playlist_title\" )) | .[0].playlistWatchUrl" "$CUSTOM_PLAYLISTS")
      urlForAll=$(jq -r ". | map(select(.name == \"$playlist_title\" )) | .[0].playlistUrl" "$CUSTOM_PLAYLISTS")
      search_results=$(run_yt_dlp "$url")
      playlist_explorer
    done
    ;;
  "Liked Videos")
    url="https://www.youtube.com/playlist?list=LL"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  "Watch Later")
    url="https://www.youtube.com/playlist?list=WL"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  "Watch History")
    url="https://www.youtube.com/feed/history"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  Clips)
    url="https://www.youtube.com/feed/clips"
    search_results=$(run_yt_dlp "$url")
    playlist_explorer
    ;;
  "Edit Config")
    if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
      $PREFERRED_EDITOR "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "$EDITOR" >/dev/null 2>&1; then
      $EDITOR "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "open" >/dev/null 2>&1; then
      open "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "xdg-open" >/dev/null 2>&1; then
      xdg-open "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    else
      send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
    fi
    load_config
    ;;
  Miscellaneous)
    while true; do
      action="$(printf "\
${CYAN}󰵀${RESET}  Explore Channels
${CYAN}󰐑${RESET}  Explore Playlists
${CYAN}󱘢${RESET}  Search History
${CYAN}󰆺${RESET}  New Custom Command
${CYAN}󰡦${RESET}  Custom Commands
${CYAN}${RESET}  Edit Search History
${CYAN}󰤀${RESET}  Edit Custom Playlists
${CYAN}󱄢${RESET}  Edit MPV Config
${CYAN}󰮆${RESET}  Edit yt-dlp Config
${CYAN}󱘫${RESET}  Edit Custom Commands
${CYAN}󰓦${RESET}  Sync YouTube Subscriptions
${RED}󰆴${RESET}  Clear Search History
${RED}${RESET}  Back
${RED}󰈆${RESET}  Exit
" | launcher "Select Action" | sed 's/.*  //g')"
      case "$action" in
      Explore\ Channels)
        unset search_term
        while true; do
          clear
          [ -z "$search_term" ] && search_term=$(prompt "What channel would you like to explore?") && url="https://www.youtube.com/results?search_query=$search_term&sp=EgIQAg%253D%253D" && channels_data=$(
            if command -v "gum" >/dev/null 2>&1; then
              gum spin --show-output -- yt-dlp "$url" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER --playlist-start 1 --playlist-end $NO_OF_SEARCH_RESULTS
            else
              echo "Loading..." >/dev/stderr
              yt-dlp "$url" -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER --playlist-start 1 --playlist-end $NO_OF_SEARCH_RESULTS
            fi
          ) && download_preview_images "$channels_data" "https:"
          clear
          channels=$(echo "$channels_data" | jq '.entries[].channel' -r)
          export channels_data SHELL
          channel_name=$(printf "%s\nBack\nExit" "$channels" | launcher_with_preview "Select Channel" "$PREVIEW_SCRIPT_FOR_CHANNELS_EXPLORER")
          channel_name="$(echo "$channel_name" | sed 's/"/\\"/g;s/ *$//g' | tr -d "\n")"
          [ "$channel_name" = "Exit" ] && byebye
          [ "$channel_name" = "Back" ] || [ "$channel_name" = "" ] && break
          channel="$(echo "$channels_data" | jq ".entries|map(select(.channel == \"$channel_name\"))|.[0]")"
          channels_explorer
        done
        unset channels_data
        ;;
      Explore\ Playlists)
        clear
        search_term=$(prompt "What playlist would you like to explore?")
        url="https://www.youtube.com/results?search_query=$search_term&sp=EgIQAw%253D%253D"
        PLAYLISTS_EXTRA_ARGS="--playlist-start 1 --playlist-end $NO_OF_SEARCH_RESULTS"
        playlists_explorer
        unset PLAYLISTS_EXTRA_ARGS
        ;;
      Search\ History)
        clear
        while true; do
          ! [ -s "$CLI_CACHE_DIR/search_history.txt" ] && echo "No search history or disabed" && sleep "$NOTIFICATION_DURATION" && main
          search_term="$(printf "$(tac "$CLI_CACHE_DIR/search_history.txt")\nBack" | launcher "Search for" | jq -Rr '@uri')"
          [ "$search_term" = "Back" ] || [ "$search_term" = "" ] && break
          url="https://www.youtube.com/results?search_query=$search_term&sp=EgIQAQ%253D%253D"
          search_results=$(run_yt_dlp "$url")
          playlist_explorer
        done
        ;;
      New\ Custom\ Command)
        clear
        custom_yt_dlp_cmd=$(prompt "Enter the custom yt-dlp command" "yt-dlp -J --flat-playlist --extractor-args youtubetab:approximate_date $PREFERRED_BROWSER https://youtube.com")
        custom_cmds="[]"
        [ -s "$F_CUSTOM_CMDS" ] && custom_cmds=$(cat "$F_CUSTOM_CMDS")
        custom_cmd_name=$(prompt "Enter the name of the new custom command")
        custom_cmd="
          {
            \"name\": \"$custom_cmd_name\",
            \"cmd\": \"$custom_yt_dlp_cmd\"
          }
        "
        echo "$custom_cmds" | jq ".+=[$custom_cmd]" >"$F_CUSTOM_CMDS" && send_notification "successfully added to custom cmds" || send_notification "Failed to add to custom cmds"

        echo Running new custom command...
        search_results=$(
          if command -v "gum" >/dev/null 2>&1; then
            gum spin --show-output --show-error -- $custom_yt_dlp_cmd || send_notification "Failed to fetch data : ("
          else
            echo "Loading..." >/dev/stderr
            $custom_yt_dlp_cmd || send_notification "Failed to fetch data : ("
          fi
        )

        playlist_explorer
        ;;
      Custom\ Commands)
        ! [ -s "$F_CUSTOM_CMDS" ] && send_notification "You dont have any custom cmds. Create them here: <$F_CUSTOM_CMDS>"
        while true; do
          custom_cmd_name=$(printf "%s\nBack" "$(jq -r '.[].name' "$F_CUSTOM_CMDS")" | launcher "Select Custom Data Loader Command To Run")
          [ "$custom_cmd_name" = "Back" ] || [ "$custom_cmd_name" = "" ] && break
          custom_yt_dlp_cmd=$(jq -r ". | map(select(.name == \"$custom_cmd_name\" )) | .[0].cmd" "$F_CUSTOM_CMDS")
          echo Running custom command...
          search_results=$(
            if command -v "gum" >/dev/null 2>&1; then
              gum spin --show-output --show-error -- $custom_yt_dlp_cmd || send_notification "Failed to fetch data : ("
            else
              echo "Loading..." >/dev/stderr
              $custom_yt_dlp_cmd || send_notification "Failed to fetch data : ("
            fi
          )
          playlist_explorer
        done
        ;;
      Edit\ Search\ History)
        if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
          $PREFERRED_EDITOR "$CLI_CACHE_DIR/search_history.txt"
        elif command -v "$EDITOR" >/dev/null 2>&1; then
          $EDITOR "$CLI_CACHE_DIR/search_history.txt"
        elif command -v "open" >/dev/null 2>&1; then
          open "$CLI_CACHE_DIR/search_history.txt"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "$CLI_CACHE_DIR/search_history.txt"
        else
          send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
        fi
        ;;
      Edit\ Custom\ Playlists)
        if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
          $PREFERRED_EDITOR "$CLI_CACHE_DIR/custom_playlists.txt"
        elif command -v "$EDITOR" >/dev/null 2>&1; then
          $EDITOR "$CLI_CONFIG_DIR/custom_playlists.json"
        elif command -v "open" >/dev/null 2>&1; then
          open "$CLI_CONFIG_DIR/custom_playlists.json"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "$CLI_CONFIG_DIR/custom_playlists.json"
        else
          send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
        fi
        ;;
      Edit\ Custom\ Commands)
        if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
          $PREFERRED_EDITOR "$F_CUSTOM_CMDS"
        elif command -v "$EDITOR" >/dev/null 2>&1; then
          $EDITOR "$F_CUSTOM_CMDS"
        elif command -v "open" >/dev/null 2>&1; then
          open "$F_CUSTOM_CMDS"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "$F_CUSTOM_CMDS"
        else
          send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
        fi
        ;;

      Edit\ MPV\ Config)
        if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
          $PREFERRED_EDITOR "${XDG_CONFIG_HOME:-"$HOME"/.config}/mpv/mpv.conf"
        elif command -v "$EDITOR" >/dev/null 2>&1; then
          $EDITOR "${XDG_CONFIG_HOME:-"$HOME"/.config}/mpv/mpv.conf"
        elif command -v "open" >/dev/null 2>&1; then
          open "${XDG_CONFIG_HOME:-"$HOME"/.config}/mpv/mpv.conf"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "${XDG_CONFIG_HOME:-"$HOME"/.config}/mpv/mpv.conf"
        else
          send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
        fi
        ;;
      "Edit yt-dlp Config")
        if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
          $PREFERRED_EDITOR "${XDG_CONFIG_HOME:-"$HOME"/.config}/yt-dlp/config"
        elif command -v "$EDITOR" >/dev/null 2>&1; then
          $EDITOR "${XDG_CONFIG_HOME:-"$HOME"/.config}/yt-dlp/config"
        elif command -v "open" >/dev/null 2>&1; then
          open "${XDG_CONFIG_HOME:-"$HOME"/.config}/yt-dlp/config"
        elif command -v "xdg-open" >/dev/null 2>&1; then
          xdg-open "${XDG_CONFIG_HOME:-"$HOME"/.config}/yt-dlp/config"
        else
          send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open"
        fi
        ;;
      Clear\ Search\ History)
        confirm "Are you sure you want to clear your search history ($CLI_CACHE_DIR/search_history.txt)?" && rm "$CLI_CACHE_DIR/search_history.txt"
        ;;
      Sync\ YouTube\ Subscriptions)
        if confirm "This will erase your local subs, proceed?"; then
          echo "Syncing subscriptions..."
          [ -n "$PREFERRED_BROWSER" ] && channels_data=$(yt-dlp "https://www.youtube.com/feed/channels" --flat-playlist $PREFERRED_BROWSER -J) || send_notification "Failed to fetch subscriptions (please set preferred browser in config"
          download_preview_images "$channels_data" "https:"
          [ -n "$channels_data" ] && ! [ "$channels_data" = "null" ] && echo "$channels_data" >"$CLI_CONFIG_DIR/subscriptions.json" && send_notification "successfully imported yt subs" || send_notification "Failed to fetch subscriptions"
        else
          send_notification "Sync cancelled"
        fi
        ;;
      Back | "")
        break
        ;;
      Exit)
        byebye
        ;;
      *)
        echo invalid action
        sleep "$NOTIFICATION_DURATION"
        ;;
      esac
    done
    ;;
  Exit | "")
    byebye
    ;;
  *)
    echo invalid action
    sleep "$NOTIFICATION_DURATION"
    ;;
  esac
  main
}

usage() {
  printf "\
A script written to browse youtube from the terminal

Usage: %s [arguments] [options] 

Commandline options override the config

Options:
  -S, --search [Search Terms]
    search for a video
  -e, --edit-config
    edit $CLI_NAME config file
  --rofi-theme <path>
    set the path to your rofi config file
  --disown-streaming-process
    disown the streaming process so you can contine streaming even if you close $CLI_NAME
  --no-disown-streaming-process
    don't disown the streaming process
  --disown-downloading-process
    disown the downloading process so you can contine streaming even if you close $CLI_NAME
  --no-disown-downloading-process
    don't disown the downloading process
  -s <selector>,--preferred-selector <selector> [fzf/rofi]
    set the preferred selector for $CLI_NAME to use
  -p <player>,--player <player> [mpv/vlc]
    set the video player for $CLI_NAME to use
  -x <extension>,--extension <extension> [name of extension file at $CLI_EXTENSION_DIR]
    set the extension for $CLI_NAME to use
  --preview
    enable the preview window
  --no-preview
    disable the preview window
  -E, --generate-desktop-entry
    print the desktop entry and exit
  -h, --help
    Show this help message and exit
  -v, --version
    print the $CLI_NAME version and exit

arguments:
  completions
    generates shell completions for $CLI_NAME

Examples:
  $CLI_NAME --generate-desktop-entry
  $CLI_NAME completions --fish 
" "$CLI_NAME"
  exit "$1"
}

# load config and default vars
load_config
force_update=0
CURRENT_TIME=$(date +%s)

while [ $# -gt 0 ]; do
  case "$1" in
  -h | --help)
    usage 0
    ;;
  -v | --version)
    echo "$CLI_NAME v$CLI_VERSION Copyright © 2024 $CLI_AUTHOR projects"
    exit 0
    ;;
  -e | --edit-config)
    if command -v "$PREFERRED_EDITOR" >/dev/null 2>&1; then
      $PREFERRED_EDITOR "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "$EDITOR" >/dev/null 2>&1; then
      $EDITOR "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "open" >/dev/null 2>&1; then
      open "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    elif command -v "xdg-open" >/dev/null 2>&1; then
      xdg-open "$CLI_CONFIG_DIR/${CLI_NAME}.conf"
    else
      send_notification "Could not find preferred editor ($PREFERRED_EDITOR) or editor env ($EDITOR) or xdg-open or open" && exit 1
    fi

    byebye
    ;;
  -U | --update)
    check_update "A new version of $CLI_NAME has been found would you like to upgrade? (y/n)"
    exit 0
    ;;
  -S | --search)
    CMD_ACTION="Search"
    CMD_SEARCH_TERMS="$2"
    shift
    ;;
  -s | --preferred-selector)
    [ -n "$2" ] || usage 1
    PREFERRED_SELECTOR="$2"
    shift
    ;;
  --preview)
    ENABLE_PREVIEW="true"
    ;;
  --no-preview)
    ENABLE_PREVIEW="false"
    ;;
  --disown-streaming-process)
    DISOWN_STREAMING_PROCESS="true"
    ;;
  --no-disown-streaming-process)
    DISOWN_STREAMING_PROCESS="false"
    ;;
  --disown-downloading-process)
    DISOWN_DOWNLOADING_PROCESS="true"
    ;;
  --no-disown-downloading-process)
    DISOWN_DOWNLOADING_PROCESS="false"
    ;;
  --rofi-theme)
    [ -n "$2" ] || usage 1
    ROFI_THEME="$2"
    shift
    ;;
  -p | --player)
    [ -n "$2" ] || usage 1
    PLAYER="$2"
    shift
    ;;
  -E | --generate-desktop-entry)
    echo "
[Desktop Entry]
Name=$CLI_NAME
Type=Application
version=$CLI_VERSION
Path=$HOME
Comment=Browse Youtube from the terminal
Terminal=false
Icon=$CLI_DIR/assets/logo.png
Exec=$0 --preferred-selector rofi
Categories=Education
    "
    exit 0
    ;;
  completions)
    [ -n "$2" ] || usage 1
    case "$2" in
    -f | --fish)
      echo "\
# --- completions ---
complete -c $CLI_NAME --no-files --arguments \"completions\" --condition 'not __fish_contains_opt sort-by S e edit-config s preferred-selector  E generate-desktop-entry rofi-theme'

complete -c $CLI_NAME --no-files --short-option h --long-option help --description 'Print a short help text and exit'
complete -c $CLI_NAME --no-files --short-option v --long-option version --description 'Print a short version string and exit' --condition 'not __fish_seen_subcommand_from completions'

complete -c $CLI_NAME --no-files --short-option e --long-option edit-config --description 'Edit $CLI_NAME config file' --condition 'not __fish_seen_subcommand_from completions'
complete -c $CLI_NAME --no-files --short-option U --long-option update --description 'update the script' --condition 'not __fish_seen_subcommand_from completions'
complete -c $CLI_NAME --no-files --short-option p --long-option player --description 'the video player to use' --condition 'not __fish_seen_subcommand_from completions' --exclusive --arguments 'mpv vlc'
complete -c $CLI_NAME --no-files --short-option x --long-option extension --description 'The extension to use' --condition 'not __fish_seen_subcommand_from completions' --exclusive --arguments \"(command ls $CLI_EXTENSION_DIR)\"
complete -c $CLI_NAME --no-files --short-option s --long-option preferred-selector --description 'your preferred selector' --condition 'not __fish_seen_subcommand_from completions' --exclusive --arguments 'fzf rofi'
complete -c $CLI_NAME --no-files --short-option E --long-option generate-desktop-entry --description 'generate desktop entry info' --condition 'not __fish_seen_subcommand_from completions' 

complete -c $CLI_NAME --no-files --long-option preview --description 'enable preview window' --condition 'not __fish_seen_subcommand_from completions' 
complete -c $CLI_NAME --no-files --long-option no-preview --description 'disable preview window' --condition 'not __fish_seen_subcommand_from completions' 

complete -c $CLI_NAME --force-files --long-option rofi-theme --description 'the path to your rofi config file' --condition 'not __fish_seen_subcommand_from completions' 

complete -c $CLI_NAME --no-files --short-option S --long-option search --description 'the terms you want to search' --condition 'not __fish_seen_subcommand_from completions' 

complete -c $CLI_NAME --no-files --short-option z --long-option zsh --description 'print zsh completions' --condition '__fish_seen_subcommand_from completions'
complete -c $CLI_NAME --no-files --short-option b --long-option bash --description 'print bash completions' --condition '__fish_seen_subcommand_from completions'
complete -c $CLI_NAME --no-files --short-option f --long-option fish --description 'print fish completions' --condition '__fish_seen_subcommand_from completions'
    "
      ;;
    -b | --bash)
      # TODO: write bash completions
      echo Contribute to $CLI_NAME by writing bash completions
      ;;
    -z | --zsh)
      # TODO: write zsh completions
      echo Contribute to $CLI_NAME by writing zsh completions
      ;;
    -h | --help)
      echo "\
Generate shell completions for $CLI_NAME

Options:
  --fish
    print fish completions and exit
  --bash
    print bash completions and exit
  --zsh
    print zsh completions and exit

Example:
  $CLI_NAME completions --fish
  $CLI_NAME completions --bash
  $CLI_NAME completions --zsh
    "
      ;;
    *)
      echo "\
Generate shell completions for $CLI_NAME

Options:
  --fish
    print fish completions and exit
  --bash
    print bash completions and exit
  --zsh
    print zsh completions and exit

Example:
  $CLI_NAME completions --fish
  $CLI_NAME completions --bash
  $CLI_NAME completions --zsh
    "
      exit 1
      ;;

    esac
    exit 0
    ;;
  -x | --extension)
    [ -n "$2" ] || usage 1
    . "$CLI_EXTENSION_DIR/$2"
    shift
    ;;
  *)
    usage 1
    ;;
  esac
  shift
done

# Check for updates if enabled
if [ "$UPDATE_CHECK" = "true" ]; then
  # setup for ui
  timestamp_file="$CLI_CACHE_DIR/.last_update_check"

  # Time interval in seconds (6 hours = 21600 seconds)
  interval=$((6 * 60 * 60))

  # Determine whether to run the update check
  current_time=$(date +%s)
  last_check_time=$(cat "$timestamp_file" 2>/dev/null || echo 0)

  if ((current_time - last_check_time >= interval)); then
    check_update "A new version of $CLI_NAME has been found would you like to upgrade? (y/n)"
    echo "$current_time" >"$timestamp_file"
  fi
fi
if [ "$WELCOME_SCREEN" = "true" ]; then
  # setup for ui
  timestamp_file="$CLI_CACHE_DIR/.last_welcome"

  # Time interval in seconds (30dys)
  interval=$((30 * 24 * 60 * 60))

  # Determine whether to run the update check
  current_time=$(date +%s)
  last_check_time=$(cat "$timestamp_file" 2>/dev/null || echo 0)

  if ((current_time - last_check_time >= interval)); then
    welcome
    echo "$current_time" >"$timestamp_file"
  fi
fi
core_dep_ch

# exports
export -f generate_sha256 fzf_preview

main
